package kernel

import (
	"os"
	"time"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	Log *zap.Logger
	// zap日志等级映射
	ZapLevelMap = map[string]zapcore.Level{
		"debug":  zapcore.DebugLevel,
		"info":   zapcore.InfoLevel,
		"warn":   zapcore.WarnLevel,
		"error":  zapcore.ErrorLevel,
		"dpanic": zapcore.DPanicLevel,
		"panic":  zapcore.PanicLevel,
		"fatal":  zapcore.FatalLevel,
	}
)

// 构建zap日志
func NewLog(config *LogConfig) (*zap.Logger, error) {
	// 编码配置
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "message",
		StacktraceKey:  "stack-trace",
		LineEnding:     zapcore.DefaultLineEnding,
		FunctionKey:    zapcore.OmitKey,
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
		EncodeName:     zapcore.FullNameEncoder,
		EncodeTime: func(t time.Time, pae zapcore.PrimitiveArrayEncoder) {
			pae.AppendString(config.Prefix + config.ConsoleSeparator + t.Format(config.TimeFormat))
		},
		ConsoleSeparator: config.ConsoleSeparator,
	}

	encoder := zapcore.NewConsoleEncoder(encoderConfig)
	if config.Encoding == "json" {
		encoder = zapcore.NewJSONEncoder(encoderConfig)
	}

	// 日志选项
	options := []zap.Option{}
	if config.Caller {
		options = append(options, zap.AddCaller())
	}
	if config.StackTrace {
		options = append(options, zap.AddStacktrace(zap.ErrorLevel))
	}

	// 日志核心
	cores := make([]zapcore.Core, 0, 7)

	// 标准核心
	// 日志输出
	syncers := make([]zapcore.WriteSyncer, 0, 10)
	// 标准输出
	syncers = append(syncers, zapcore.AddSync(os.Stdout))
	// 文件输出
	if config.EnableFile {
		for _, path := range config.FileConfig.OutputPaths {
			l := &lumberjack.Logger{
				Filename:   path,
				MaxAge:     config.FileConfig.MaxAge,
				MaxSize:    config.FileConfig.MaxSize,
				MaxBackups: config.FileConfig.MaxBackups,
				Compress:   config.FileConfig.Compress,
				LocalTime:  true,
			}
			syncers = append(syncers, zapcore.AddSync(l))
		}
	}
	cores = append(cores,
		zapcore.NewCore(
			encoder,
			zap.CombineWriteSyncers(syncers...),
			func(l zapcore.Level) zap.LevelEnablerFunc {
				return func(lvl zapcore.Level) bool {
					return lvl != zap.ErrorLevel
				}
			}(config.Level.ZapLevel()),
		),
	)

	// 错误日志单独处理
	// 日志输出
	syncers = make([]zapcore.WriteSyncer, 0, 10)
	// 文件输出
	if config.EnableFile {
		for _, path := range config.FileConfig.ErrorOutputPaths {
			l := &lumberjack.Logger{
				Filename:   path,
				MaxAge:     config.FileConfig.MaxAge,
				MaxSize:    config.FileConfig.MaxSize,
				MaxBackups: config.FileConfig.MaxBackups,
				Compress:   config.FileConfig.Compress,
				LocalTime:  true,
			}
			syncers = append(syncers, zapcore.AddSync(l))
		}
	}
	cores = append(cores,
		zapcore.NewCore(
			encoder,
			zap.CombineWriteSyncers(syncers...),
			func(l zapcore.Level) zap.LevelEnablerFunc {
				return func(lvl zapcore.Level) bool {
					return lvl == zap.ErrorLevel
				}
			}(config.Level.ZapLevel()),
		),
	)

	return zap.New(zapcore.NewTee(cores...), options...), nil
}

// 日志配置
type LogConfig struct {
	Prefix           string         `mapstructure:"prefix" json:"prefix"`
	Level            LogLevel       `mapstructure:"level" json:"level"`
	Encoding         string         `mapstructure:"encoding" json:"encoding"`
	Caller           bool           `mapstructure:"caller" json:"caller"`
	StackTrace       bool           `mapstructure:"stack-trace" json:"stack-trace"`
	TimeFormat       string         `mapstructure:"time-format" json:"time-format"`
	ConsoleSeparator string         `mapstructure:"console-separator" json:"console-separator"`
	EnableFile       bool           `mapstructure:"enable-file" json:"enable-file"`
	FileConfig       *LogFileConfig `mapstructure:"file-config" json:"file-config"`
}

// 日志文件配置
type LogFileConfig struct {
	MaxAge           int      `mapstructure:"max-age" json:"max-age"`
	MaxSize          int      `mapstructure:"max-size" json:"max-size"`
	MaxBackups       int      `mapstructure:"max-backups" json:"max-backups"`
	Compress         bool     `mapstructure:"compress" json:"compress"`
	OutputPaths      []string `mapstructure:"output-paths" json:"output-paths"`
	ErrorOutputPaths []string `mapstructure:"error-output-paths" json:"error-output-paths"`
}

// 日志等级
type LogLevel string

func (l LogLevel) String() string {
	return string(l)
}

// 转化成zap level
func (l LogLevel) ZapLevel() zapcore.Level {
	z, ok := ZapLevelMap[l.String()]
	if !ok {
		return ZapLevelMap["debug"]
	}

	return z
}
